<?php

declare(strict_types=1);

namespace EthanZ\HyperfExt\EsData;

use Elasticsearch\Client;
use Hyperf\Elasticsearch\ClientBuilderFactory;
use Hyperf\Guzzle\RingPHP\PoolHandler;
use Hyperf\Utils\ApplicationContext;

/**
 * es组件类
 */
abstract class ElasticSearchBase
{


    /**
     * 必须实现数据库数据类型对应es类型
     *
     * @return array
     */
    abstract public function dbToEsMapping(): array;


    /**
     * 设置索引名
     *
     * @return string
     */
    abstract public function indexName(): string;


    /**
     * 设置对应表名
     *
     * @return array
     */
    abstract public function setTableName(): array;


    /**
     * 重建索引数据
     *
     * @param int $per
     *
     * @return bool
     */
    abstract public function reIndexData(int $per = 100): bool;


    /**
     * 获取es连接
     *
     * @return Client
     * @throws
     */
    public function getEsClient(): Client
    {
        $container = ApplicationContext::getContainer();
        $builder   = $container->get(ClientBuilderFactory::class)->create();
        $handler   = make(PoolHandler::class, (array)config('elastic_search.default.option', []));
        $builder->setHandler($handler);

        return $builder->setHosts((array)config('elastic_search.default.host', []))->build();
    }


    /**
     * 获取类型默认值
     *
     * @return array
     */
    public function mappingTypeDefault(): array
    {
        return [
            'varchar'  => '',
            'char'     => '',
            'text'     => '',
            'bigint'   => 0,
            'int'      => 0,
            'smallint' => 0,
            'tinyint'  => 0,
            'decimal'  => 0.0,
            'float'    => 0.0,
            'date'     => date('Y-m-d H:i:s', 0),
            'datetime' => date('Y-m-d H:i:s', 0),
            'bool'     => false,
            'object'   => null,
            'array'    => [],
        ];
    }


    /**
     * es类型映射
     *
     * @return string[][]
     */
    public function esFieldTypeMapping(): array
    {
        return [
            'varchar'  => [
                'type' => 'keyword',
            ],
            'char'     => [
                'type' => 'keyword',
            ],
            'text'     => [
                'type' => 'text',
            ],
            'bigint'   => [
                'type' => 'long',
            ],
            'int'      => [
                'type' => 'integer',
            ],
            'smallint' => [
                'type' => 'short',
            ],
            'tinyint'  => [
                'type' => 'byte',
            ],
            'decimal'  => [
                'type' => 'float',
            ],
            'float'    => [
                'type' => 'float',
            ],
            'bool'     => [
                'type' => 'boolean',
            ],
            'object'   => [
                'type' => 'object',
            ],
            'array'    => [
                'type' => 'object',
            ],
            'nested'   => [
                'type' => 'nested',
            ],
            'date'     => [
                'type' => 'date'
            ],
            'datetime' => [
                'type' => 'date'
            ],
        ];
    }


    /**
     * 验证数据
     *
     * @param array $params
     *
     * @return array
     */
    public function matchData(array $params): array
    {
        $body         = [];
        $typeDefault  = $this->mappingTypeDefault();
        $modelList    = $this->dbToEsMapping();
        $defaultValue = $this->defaultValue();
        foreach ($modelList as $key => $type) {
            $value      = $params[$key] ?? $defaultValue[$key] ?? $typeDefault[$type];
            $body[$key] = $value;
        }

        return $body;
    }


    /**
     * 设置默认值
     *
     * @return array
     */
    public function defaultValue(): array
    {
        return [];
    }


    /**
     * 获取映射
     *
     * @param array $params
     *
     * @return array
     */
    public function getMapping(array $params): array
    {
        $mappingList = [];
        $fieldType   = self::esFieldTypeMapping();
        foreach ($params as $key => $param) {
            if (!isset($fieldType[$param])) {
                var_dump($key);
            }
            $mappingList[$key] = $fieldType[$param];
        }

        return $mappingList;
    }


    /**
     * 删除索引
     *
     * @return bool
     */
    public function deleteIndex(): bool
    {
        $params = ['index' => $this->indexName()];
        $info   = $this->getEsClient()->indices()->delete($params);

        return isset($info['acknowledged']) && $info['acknowledged'];
    }


    /**
     * 生成索引
     *
     * @return bool
     */
    public function createEsIndex(): bool
    {
        //检查索引是否已存在
        if ($this->checkIndexIsExisted()) {
            return true;
        }

        //设置mapping映射
        $params = [
            'index' => $this->indexName(),
            'body'  => [
                'mappings' => [
                    'properties' => $this->getMapping($this->dbToEsMapping())
                ]
            ],
        ];

        //创建索引
        $info = $this->getEsClient()->indices()->create($params);

        return isset($info['acknowledged']) && $info['acknowledged'];
    }


    /**
     * 清理索引内的数据
     */
    public function clearIndexData(): void
    {
        $params = [
            'index' => $this->indexName(),
            'body'  => [
                'query' => [
                    'bool' => [
                        'filter' => ['range' => ['id' => ['gt' => 0]]]
                    ]
                ]
            ]
        ];
        $this->getEsClient()->deleteByQuery($params);
    }


    /**
     * 判断索引是否存在
     *
     * @return bool
     */
    public function checkIndexIsExisted(): bool
    {
        $params = [
            'index' => $this->indexName(),
        ];

        return $this->getEsClient()->indices()->exists($params);
    }


    /**
     * 批量写入es数据
     *
     * @param array  $data
     * @param string $esIdField
     *
     * @return array
     */
    public function batchSaveToEs(array $data, string $esIdField): array
    {
        $client  = $this->getEsClient();
        $putData = [];
        foreach ($data as $item) {
            $putData['body'][] = [
                'index' => [
                    '_index' => $this->indexName(),
                    '_id'    => $item[$esIdField] ?? '',
                ]
            ];

            $putData['body'][] = self::matchData($item);
        }

        $res = [];
        if ($putData) {
            $res = $client->bulk($putData);
        }

        return $res;
    }


    /**
     * 创建数据
     *
     * @param array $params
     * @param       $id
     *
     * @return false|void
     */
    public function createData(array $params, $id)
    {
        if (empty($params)) {
            return false;
        }
        $body      = self::matchData($params);
        $indexData = [
            'index' => $this->indexName(),
            'id'    => $id,
            'body'  => $body,
        ];
        $this->getEsClient()->index($indexData);
    }


    /**
     * 更新
     *
     * @param array $params
     * @param       $id
     *
     * @return false|void
     */
    public function updateData(array $params, $id)
    {
        if (empty($params)) {
            return false;
        }

        $body      = self::matchData($params);
        $indexData = [
            'index' => $this->indexName(),
            'id'    => $id,
            'body'  => ['doc' => $body],
        ];
        $this->getEsClient()->update($indexData);
    }


    /**
     * 删除数据
     *
     * @param int $id
     *
     * @return false|void
     */
    public function deleteData(int $id)
    {
        if (empty($id)) {
            return false;
        }

        $indexData = [
            'index' => $this->indexName(),
            'id'    => $id,
        ];

        $this->getEsClient()->delete($indexData);
    }


    /**
     * 查询查询
     *
     * @param array $queryParams
     * @param array $sort
     * @param int   $page
     * @param int   $pageSize
     *
     * @return array
     */
    public function listQuery(array $queryParams, array $sort = [], int $page = 1, int $pageSize = 15): array
    {
        $client = $this->getEsClient();
        $params = [
            'body' => [
                'query' => $queryParams
            ]
        ];
        if ($sort) {
            $params['body']['sort'] = $sort;
        }

        $params['index'] = $this->indexName();
        $params['size']  = $pageSize;
        $params['from']  = (int)abs(($page - 1) * $pageSize) ?: 0;
        $res             = $client->search($params);

        return $this->resolveQueryListResult($res);
    }


    /**
     * 解析查询结果
     *
     * @param array $res
     *
     * @return array
     */
    public function resolveQueryListResult(array $res): array
    {
        $data          = [];
        $data['total'] = $res['hits']['total']['value'] ?? 0;
        $data['list']  = (function (array $hits) {
            $list = [];
            foreach ($hits as $item) {
                if (isset($item['_source'])) {
                    $list[] = $item['_source'];
                }
            }

            return $list;
        })(
            $res['hits']['hits'] ?? []
        );

        return $data;
    }
}
